/*
 * log.c
 *
 *  Created on: Sep 6, 2010
 *      Author: thienlong
 */

#include "log.h"
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#define _STDARG_H
#include <stdarg.h>
#include "stringbuffer.h"
#include <assert.h>

#define LOG_EXTENSION_LEN 32

typedef struct {
	pthread_mutex_t log_lock;
	FILE *logfile;
	char *filename;
	int log_base_level;
	long max_bytes;
	int max_backup;
	long file_len;
	int backup_index;
	char **backup_files;
} logger;


static logger log;

void discard_logging(int severity, const char *msg) {
	/* do nothing */
}

/**
 * check whether a given filename is existence.
 * @return 1 if it exists or 0 otherwise.
 */
int file_exists(const char* filename) {
	FILE * file = fopen(filename, "r");
	if (file)
	{
		fclose(file);
		return 1;
	}
	return 0;
}

void set_log_config(char *filename, const char *base_level, int max_bytes,
		int maxbackup) {
	memset(&log, 0, sizeof(log));
	pthread_mutex_init(&log.log_lock, NULL);
	log.filename = filename;
	log.max_bytes = max_bytes;
	log.max_backup = maxbackup;
	if (strcmp(base_level, "DEBUG") == 0) {
		log.log_base_level = LOG_DEBUG;
	} else if (strcmp(base_level, "INFO") == 0) {
		log.log_base_level = LOG_INFO;
	} else if (strcmp(base_level, "WARN") == 0) {
		log.log_base_level = LOG_WARN;
	} else if (strcmp(base_level, "ERROR") == 0) {
		log.log_base_level = LOG_ERROR;
	} else if (strcmp(base_level, "OFF") == 0) {
		log.log_base_level = LOG_OFF;
	}
	if (filename) {
		log.logfile = fopen(filename, "a");
		if (log.logfile) {
			fseek(log.logfile, 0, SEEK_END);
			log.file_len = ftell(log.logfile);
		}
		int len = strlen(filename);
		log.backup_files = calloc(log.max_backup + 1, sizeof(char*));
		int i = 1;
		char strnum[LOG_EXTENSION_LEN];
		for (; i <= log.max_backup; ++i) {
			log.backup_files[i] = strndup(filename, len + LOG_EXTENSION_LEN);
			log.backup_files[i][len] = '.';
			long_to_string(strnum, i);
			strcat(log.backup_files[i], strnum);
			if (file_exists(log.backup_files[i])) {
				log.backup_index = i;
			}
		}
		log.backup_files[0] = filename;
	}
}

/**
 * non thread-safe
 */
#define RENAME_LOG_FILE(index) \
{	\
	int i = index;	\
	for (;i >= 0 ; --i) {	\
		rename(log.backup_files[i], log.backup_files[i + 1]);	\
	}	\
}
#define UPDATE_LOG_STATUS() \
do {						\
	log.file_len = ftell(log.logfile);			\
	if (log.file_len >= log.max_bytes) {	\
		fclose(log.logfile);			\
		log.file_len = 0;		\
		if (log.backup_index == log.max_backup) {	\
			assert(remove(log.backup_files[log.max_backup]) == 0); \
			RENAME_LOG_FILE(log.max_backup - 1);	\
		} else {										\
			RENAME_LOG_FILE(log.backup_index);	\
			log.backup_index += 1;	\
		}	\
		log.logfile = fopen(log.filename, "a");\
	}	\
} while (0)

void write_log(int severity, const char *msg) {
	if (severity < log.log_base_level) {
		return;
	}
	if (!log.logfile) {
		return;
	}
	const char *s;
	switch (severity) {
	case LOG_DEBUG:
		s = "DEBUG";
		break;
	case LOG_INFO:
		s = "INFO ";
		break;
	case LOG_WARN:
		s = "WARN ";
		break;
	case LOG_ERROR:
		s = "ERROR";
		break;
	default:
		s = "s";
		break;
	}
	pthread_mutex_lock(&log.log_lock);
	fprintf(log.logfile, "[%s] %s\n", s, msg);
	fflush(log.logfile);
	UPDATE_LOG_STATUS();
	pthread_mutex_unlock(&log.log_lock);
}

void fprintf_log(int severity, const char *fmt, ...) {
	if (severity < log.log_base_level) {
		return;
	}
	if (!log.logfile) {
		return;
	}
	char *s = malloc(9 + strlen(fmt));
	switch (severity) {
	case LOG_DEBUG:
		strcpy(s, "[DEBUG] ");
		break;
	case LOG_INFO:
		strcpy(s, "[INFO ] ");
		break;
	case LOG_WARN:
		strcpy(s, "[WARN ] ");
		break;
	case LOG_ERROR:
		strcpy(s, "[ERROR] ");
		break;
	default:
		strcpy(s, "[s] ");
		break;
	}
	strcat(s, fmt);
	va_list argptr;
//	va_start(argptr,s);
	pthread_mutex_lock(&log.log_lock);
	vfprintf(log.logfile, s, argptr);
	fprintf(log.logfile, "\n");
	fflush(log.logfile);
	UPDATE_LOG_STATUS();
	pthread_mutex_unlock(&log.log_lock);
//	va_end(argptr);
	free(s);
}

void close_logfile() {
	if (log.logfile) {
		pthread_mutex_lock(&log.log_lock);
		fclose(log.logfile);
		pthread_mutex_unlock(&log.log_lock);
	}
}
